﻿using System;
using System.Collections.ObjectModel;
using System.Data;
using System.Linq;
using System.Text;

namespace Red.Controls.Curve.ViewModels
{
    internal class CurveViewModelUtils
    {
        internal static string TypePrefix = "type=";
        internal static string ValuesPrefix = "values=";
        internal static string TimePrefix = "time=";
        internal static string ValuePrefix = "value=";
        internal static string[] VectorPrefixes = new string[] { "X=", "Y=", "Z=", "W=" };
        internal static string[] ColorsPrefixes = new string[] { "Red=", "Green=", "Blue=", "Alpha=" };
    }

    public class CurveViewModelBuilder
    {
        public static CurveViewModel CreateFrom( string interopDataString )
        {
            string buildString = interopDataString.Trim( '{', '}' );
            string type = EatDataType( ref buildString );
            int tracksCount = 0;

            CurveViewModel model = new CurveViewModel( type );
            model.CachedDataString = interopDataString;
            PrepareTracks( type, ref model, out tracksCount );
            BeginValuesReading( ref buildString );

            while ( buildString.Length > 0 )
            {
                if ( !ReadValue( ref model, ref buildString ) )
                {
                    break;
                }
            }

            return model;
        }

        private static bool ReadValue( ref CurveViewModel model, ref string interopDataString )
        {
            interopDataString = interopDataString.TrimStart( '{' );

            if ( !interopDataString.StartsWith( CurveViewModelUtils.TimePrefix ) )
            {
                throw new DataException( "Couldn't find curve's point time" );
            }
            interopDataString = interopDataString.Substring( CurveViewModelUtils.TimePrefix.Length );

            double time = ReadValue( ref interopDataString );
            interopDataString = interopDataString.Remove( 0, 1 );

            if ( !interopDataString.StartsWith( CurveViewModelUtils.ValuePrefix )  )
            {
                throw new DataException( "Couldn't find curve's point value" );
            }
            interopDataString = interopDataString.Substring( CurveViewModelUtils.ValuePrefix.Length );

            if ( interopDataString.StartsWith( "{" ) )
            {
                interopDataString = interopDataString.TrimStart( '{' );
                var tracksPrefixes = model.Type.ToLower().Equals( "color" ) ? CurveViewModelUtils.ColorsPrefixes : CurveViewModelUtils.VectorPrefixes;

                for ( int i = 0; i < model.Tracks.Count; ++i )
                {
                    if ( i > 0 )
                    {
                        interopDataString = interopDataString.Remove( 0, 1 );
                    }

                    if ( interopDataString.StartsWith( tracksPrefixes[i] ) )
                    {
                        interopDataString = interopDataString.Remove( 0, tracksPrefixes[i].Length );
                        model.Tracks[i].AddValue( time, ReadValue( ref interopDataString ) );
                    }
                }

                interopDataString = interopDataString.TrimStart( '}' );
            }
            else //single value
            {
                model.Tracks[0].AddValue( time, ReadValue( ref interopDataString ) );
            }

            interopDataString = interopDataString.TrimStart( '}', ',' );
            return true;
        }

        private static double ReadValue( ref string interopDataString )
        {
            int numberChars = 0;
            while ( char.IsDigit( interopDataString[numberChars] ) || interopDataString[numberChars] == '.' )
            {
                numberChars++;
            }
            
            if ( numberChars == 0 )
            {
                throw new DataException( "Improper number value" );
            }

            string numberString = interopDataString.Substring( 0, numberChars );

            double numberValue = 0;
            if ( !Double.TryParse( numberString, out numberValue ) )
            {
                throw new DataException( "Improper time value" );
            }
            interopDataString = interopDataString.Substring( numberChars );

            return numberValue;
        }
        
        private static void BeginValuesReading( ref string interopDataString )
        {
            int valuesBeginningIndex = interopDataString.IndexOf( CurveViewModelUtils.ValuesPrefix );

            if ( valuesBeginningIndex >= 0 )
            {
                interopDataString = interopDataString.Substring( valuesBeginningIndex + CurveViewModelUtils.ValuesPrefix.Length );
                interopDataString = interopDataString.Trim( '[', ']' );
            }
            else
            {
                throw new DataException( "Couldn't find curve data values" );
            }
        }

        private static void PrepareTracks( string type, ref CurveViewModel model, out int tracksCount )
        {
            tracksCount = 1;
            if ( type.ToLower().StartsWith( "vector" ) || type.ToLower().Equals( "color" ) )
            {
                tracksCount = 4;
                if ( char.IsDigit( type.Last() ) )
                {
                    Int32.TryParse( type.Last().ToString(), out tracksCount );
                }
            }

            var tracksPrefixes = model.Type.ToLower().Equals( "color" ) ? CurveViewModelUtils.ColorsPrefixes : CurveViewModelUtils.VectorPrefixes;
            for ( int i = 0; i < tracksCount; ++i )
            {
                model.InsertTrack( tracksPrefixes[i].TrimEnd( '=' ) );
            }
        }

        private static string EatDataType( ref string interopDataString )
        {
            string type = string.Empty;

            if ( interopDataString.StartsWith( CurveViewModelUtils.TypePrefix ) )
            {
                interopDataString = interopDataString.Remove( 0, CurveViewModelUtils.TypePrefix.Length );

                int commaIndex = interopDataString.IndexOf( ',' );
                if ( commaIndex >= 0 )
                {
                    type = interopDataString.Substring( 0, commaIndex );
                    interopDataString = interopDataString.Substring( commaIndex + 1 );
                }
            }

            if ( type.Length == 0 )
            {
                throw new DataException( "Couldn't find curve data type" );
            }
            return type;
        }
    }

    public class CurveViewModelPacker
    {
        public static string PackToString( CurveViewModel model )
        {
            StringBuilder valuesString = new StringBuilder();

            if ( model.Tracks.Count > 0 && model.Tracks.First().Points.Count > 0 )
            {
                StringBuilder valuesFormatBuilder = new StringBuilder();
                valuesFormatBuilder.Append( "{{time={0},value=" );

                if ( model.Tracks.Count == 1 )
                {
                    valuesFormatBuilder.Append( "{1}" );
                }
                else
                {
                    valuesFormatBuilder.Append( "{{" );

                    var tracksPrefixes = model.Type.ToLower().Equals( "color" ) ? CurveViewModelUtils.ColorsPrefixes : CurveViewModelUtils.VectorPrefixes;
                    for ( int i = 0; i < model.Tracks.Count; ++i )
                    {
                        if ( i > 0 )
                        {
                            valuesFormatBuilder.Append( "," );
                        }
                        valuesFormatBuilder.AppendFormat( "{0}{{{1}}}", tracksPrefixes[i], i + 1 );
                    }

                    valuesFormatBuilder.Append( "}}" );
                }
                valuesFormatBuilder.Append( "}}" );

                string valuesFormat = valuesFormatBuilder.ToString();

                for ( int i = 0; i < model.Tracks.First().Points.Count; ++i  )
                {
                    if ( i != 0 )
                    {
                        valuesString.Append( "," );
                    }

                    string[] values = new string[] { model.Tracks[0].Points[i].Time.ToString() };
                    values = values.Concat( model.Tracks.Select( track => track.Points[i].Value.ToString() ) ).ToArray();
                        
                    valuesString.AppendFormat( valuesFormat, values );
                }
            }

            return string.Format( "{{type={0},values=[{1}]}}", model.Type, valuesString.ToString() );
        }
    }

    public class TrackViewModel
    {
        public ObservableCollection<ControlPoint> Points { get; private set; }
        public string Name { get; private set; }

        public TrackViewModel( string name )
        {
            Name = name;
            Points = new ObservableCollection<ControlPoint>();
        }

        public void AddValue( double time, double value )
        {
            Points.Add( new ControlPoint( time, value, EInteroplationType.CPT_Linear ) );
        }

        public void RemovePoint( ControlPoint pm )
        {
            Points.Remove( pm );
        }
    }


    public class CurveViewModel
    {
        public string Type { get; private set; }
        public ObservableCollection<TrackViewModel> Tracks { get; private set; }

        public string CachedDataString { get; set; }

        public CurveViewModel( string type )
        {
            Type = type;
            Tracks = new ObservableCollection<TrackViewModel>();
        }

        public void InsertTrack( string trackName )
        {
            Tracks.Add( new TrackViewModel( trackName ) );
        }
    }
}
